﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Jemgine.MapEditor
{
    [Tools.SelectableTool]
    public class TerminalTool : Tool
    {
        public override System.Drawing.Image GetIcon() { return System.Drawing.Image.FromFile("ToolIcons/terminal.bmp"); }

        private class SourceTerminal
        {
            public Vector2 Position;
            public Data.Terminal Terminal;
            public System.Type TargetType;
        }

        SourceTerminal Dragging = null;
        Vector2 DragVisualEndPoint;

        private List<SourceTerminal> EnumerateTerminals(Object Selected)
        {
            var R = new List<SourceTerminal>();
            if (Selected is Data.ScriptNode)
            {
                var Object = Selected as Data.ScriptNode;
                var BasePosition = Object.GetWorldPosition();
                for (int I = 0; I < Object.OutputTerminals.Count; ++I)
                    R.Add(new SourceTerminal { Position = Object.GetWorldPosition() + 
                        Object.GetOutputOffset(I), Terminal = Object.OutputTerminals[I] });
                for (int I = 0; I < Object.ReferenceTerminals.Count; ++I)
                    R.Add(new SourceTerminal
                    { 
                        Position = Object.GetWorldPosition() + Object.GetReferenceOffset(I), 
                        Terminal = Object.ReferenceTerminals[I],
                        TargetType = Object.ReferenceTerminalInfo[I].Type
                    });
            }
            else if (Selected is Data.Anchor)
                R.Add(new SourceTerminal { Position = (Selected as Data.Anchor).Position, Terminal = Selected as Data.Terminal });

            return R;
        }

        public override void MouseDown(MouseInputState InputState, EditorState EditorState)
        {
            Object SelectedObject = EditorState.GetSelectionObject(EditorState.Selected);
            foreach (var Item in EnumerateTerminals(SelectedObject))
                if (EditorState.PointInItem(new Vector2(1,1), Item.Position, EditorState.RawMousePosition))
                    Dragging = Item;
            DragVisualEndPoint = EditorState.RawMousePosition;
        }

        private float ClosestInputOnNode(Data.ScriptNode Node, Vector2 WorldPoint, out Data.InputTerminal Terminal)
        {
            Terminal = null;
            float Length = 0.0f;
            var NodePosition = Node.GetWorldPosition();
            for (int I = 0; I < Node.InputTerminals.Count; ++I)
            {
                float TLength = (NodePosition + Node.GetInputOffset(I) - WorldPoint).LengthSquared();
                if (Terminal == null || TLength < Length)
                {
                    Length = TLength;
                    Terminal = Node.InputTerminals[I];
                }
            }
            return Length;
        }
        
        private Data.InputTerminal FindClosestInputTerminal(Vector2 Point, EditorState EditorState)
        {
            float Length = 0.0f;
            Data.InputTerminal Result = null;

            EditorState.Map.ForEachMapElement((Element) => { return Element is Data.ScriptNode; },
                (Element) =>
                {
                    Data.InputTerminal Closest = null;
                    float TLength = ClosestInputOnNode(Element as Data.ScriptNode, Point, out Closest);
                    if (Result == null || (Closest != null && TLength < Length))
                    {
                        Length = TLength;
                        Result = Closest;
                    }

                    if (Element is Data.EntityRecord)
                        foreach (var EntityElement in (Element as Data.EntityRecord).AttachedElements)
                        {
                            if (EntityElement is Data.ScriptNode)
                            {
                                Closest = null;
                                TLength = ClosestInputOnNode(EntityElement as Data.ScriptNode, Point, out Closest);
                                if (Result == null || (Closest != null && TLength < Length))
                                {
                                    Length = TLength;
                                    Result = Closest;
                                }
                            }
                        }
                });
            return Result;
        }

        private bool CheckType(System.Type A, System.Type B)
        {
            return B == null || B.IsAssignableFrom(A) || A == typeof(Jemgine.Engine.Scripts.TemplateReference);
        }

        private Data.ReferenceTarget FindClosestReferenceTarget(Vector2 WorldPoint, System.Type TargetType,
            EditorState EditorState)
        {
            EditorState.Map.EnumerateTargets();

            Data.ReferenceTarget Result = null;
            float Length = 0.0f;

            foreach (var Target in EditorState.Map.Targets)
            {
                if (CheckType(Target.GetType(), TargetType))
                {
                    float TLength = (Target.GetWorldPosition() - WorldPoint).Length();
                    if (Result == null || TLength < Length)
                    {
                        Length = TLength;
                        Result = Target;
                    }
                }
            }

            return Result;
        }

        private Vector2 GetPositionOf(Object Target)
        {
            if (Target is Data.InputTerminal)
            {
                var Terminal = Target as Data.InputTerminal;
                return Terminal.Me.GetWorldPosition() + Terminal.Me.GetInputOffset(Terminal.LocalID);
            }
            else if (Target is Data.ReferenceTarget)
                return (Target as Data.ReferenceTarget).GetWorldPosition();
            return Vector2.Zero;
        }

        public override void MouseMove(MouseInputState InputState, EditorState EditorState)
        {
            if (Dragging == null) return;

            if (Dragging.Terminal is Data.OutputTerminal)
                DragVisualEndPoint = GetPositionOf(FindClosestInputTerminal(EditorState.RawMousePosition, EditorState));
            else if (Dragging.Terminal is Data.ReferenceTerminal)
                DragVisualEndPoint = GetPositionOf(FindClosestReferenceTarget(EditorState.RawMousePosition,
                    Dragging.TargetType, EditorState));
            else if (Dragging.Terminal is Data.Anchor)
                DragVisualEndPoint = GetPositionOf(FindClosestReferenceTarget(EditorState.RawMousePosition,
                    typeof(Data.EntityRecord), EditorState));

            if ((DragVisualEndPoint - EditorState.RawMousePosition).Length() > 2.0f)
                DragVisualEndPoint = EditorState.RawMousePosition;
        }

        public override void MouseUp(MouseInputState InputState, EditorState EditorState)
        {
            if (Dragging == null) return;

            object Target = null;

            if (Dragging.Terminal is Data.OutputTerminal)
                Target = FindClosestInputTerminal(EditorState.RawMousePosition, EditorState);
            else if (Dragging.Terminal is Data.ReferenceTerminal)
                Target = FindClosestReferenceTarget(EditorState.RawMousePosition, Dragging.TargetType, EditorState);
            else if (Dragging.Terminal is Data.Anchor)
                Target = FindClosestReferenceTarget(EditorState.RawMousePosition, typeof(Data.EntityRecord), EditorState);

            if ((GetPositionOf(Target) - EditorState.RawMousePosition).Length() > 2.0f) Target = null;

            EditorState.PerformOperation(new Operations.TerminalTargetChanged(Dragging.Terminal, Target));

            Dragging = null;
        }

        public override void DrawExtra(Data.EditorDrawData Context, EditorState EditorState)
        {
            if (Dragging != null)
            {
                Context.Renderer.Color = Color.Gold.ToVector4();
                Context.Renderer.DrawLine(Dragging.Position, DragVisualEndPoint, 0.3f);
            }
        }
    }
}
